package org.example.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.example.component.ShortLinkComponent;
import org.example.config.RabbitMQConfig;
import org.example.constant.RedisKey;
import org.example.controller.request.*;
import org.example.enums.BizCodeEnum;
import org.example.enums.DomainTypeEnum;
import org.example.enums.EventMessageTypeEnum;
import org.example.enums.ShortLinkStateEnum;
import org.example.feign.TrafficFeignService;
import org.example.interceptor.LoginInterceptor;
import org.example.manager.DomainManager;
import org.example.manager.GroupCodeMappingManager;
import org.example.manager.LinkGroupManager;
import org.example.manager.ShortLinkManager;
import org.example.model.*;
import org.example.service.ShortLinkService;
import org.example.util.CommonUtil;
import org.example.util.IDUtil;
import org.example.util.JsonData;
import org.example.util.JsonUtil;
import org.example.vo.ShortLinkVO;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 谢超
 * @since 2023-08-15
 */
@Service
@Slf4j
public class ShortLinkServiceImpl implements ShortLinkService {

    @Resource
    private ShortLinkManager shortLinkManager;

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private RabbitMQConfig rabbitMQConfig;

    @Resource
    private DomainManager domainManager;

    @Resource
    private LinkGroupManager linkGroupManager;

    @Resource
    private ShortLinkComponent shortLinkComponent;

    @Resource
    private GroupCodeMappingManager groupCodeMappingManager;

    @Resource
    private RedisTemplate<Object, Object> redisTemplate;

    @Resource
    private TrafficFeignService trafficFeignService;

    @Override
    public ShortLinkVO parseShortLinkCode(String shortLinkCode) {
        ShortLinkDO shortLinkDO = shortLinkManager.getShortLinkByCode(shortLinkCode);
        if (shortLinkDO == null) {
            return null;
        }

        ShortLinkVO shortLinkVO = new ShortLinkVO();
        BeanUtils.copyProperties(shortLinkDO, shortLinkVO);

        return shortLinkVO;
    }

    /**
     * 根据用户发送的数据进行封装，发送给MQ交换机中
     *
     * @param request
     * @return
     */
    @Override
    public JsonData addShortLink(ShortLinkAddRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        // 需要检查下是否有足够多可以进行创建
        String cacheKey = String.format(RedisKey.DAY_TOTAL_TRAFFIC, accountNo);
        // 检测key是否存在，然后递减，是否大于等于0，使用lua脚本
        // 如果key不存在，则未使用，lua返回值是0，新增流量包的时候，不用重新计算次数，直接删除key,消费的时候回计算更新
        String script = "if redis.call('get', KEYS[1]) then return redis.call('decr', KEYS[1]) else return 0 end";
        Long leftTimes = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(cacheKey), "");
        log.info("今日流量包剩余次数:{}", leftTimes);

        if (leftTimes >= 0) {
            String newOriginUrl = CommonUtil.addUrlPrefix(request.getOriginalUrl());

            request.setOriginalUrl(newOriginUrl);

            EventMessage eventMessage = EventMessage.builder()
                    .accountNo(accountNo)
                    .content(JsonUtil.obj2Json(request))
                    .messageId(IDUtil.geneSnowFlakeID().toString())
                    .eventMessageType(EventMessageTypeEnum.SHORT_LINK_ADD.name())
                    .build();

            // 发送消息
            rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(), eventMessage);
            return JsonData.buildSuccess();
        } else {
            return JsonData.buildResult(BizCodeEnum.TRAFFIC_REDUCE_FAIL);
        }
    }

    /**
     * 处理短链新增逻辑
     *
     * @param eventMessage
     */
    @Override
    public boolean handlerAddShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        // content在上面addShortLink方法内被转为了json数据，这里需要转换回来，类型为ShortLinkAddRequest
        ShortLinkAddRequest shortLinkAddRequest = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkAddRequest.class);
        // 短链域名校验
        DomainDO domainDO = checkDomain(shortLinkAddRequest.getDomainType(), shortLinkAddRequest.getDomainId(), accountNo);

        // 校验组是否合法
        LinkGroupDO linkGroupDO = checkLinkGroup(shortLinkAddRequest.getGroupId(), accountNo);

        // 短链码重复
        boolean duplicateCodeFlag = false;

        // 生成长链摘要
        String originUrlDigest = CommonUtil.MD5(shortLinkAddRequest.getOriginalUrl());

        // 创建短链码
        String shortLinkCode = shortLinkComponent.createShortLinkCode(shortLinkAddRequest.getOriginalUrl());

        // 查询之前对表加锁
        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";
        Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(shortLinkCode), accountNo, 100);

        if (result > 0) {
            // 如果shortLinkByCodeInDB为空，表示没被其他线程占用，则可以正常插入数据
            if (EventMessageTypeEnum.SHORT_LINK_ADD_LINK.name().equalsIgnoreCase(eventMessageType)) {
                // 查询短链数据
                ShortLinkDO shortLinkByCodeInDB = shortLinkManager.getShortLinkByCode(shortLinkCode);
                if (shortLinkByCodeInDB == null) {

                    boolean reduceFlag = reduceTraffic(eventMessage, shortLinkCode);
                    if (reduceFlag) {
                        ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                                .accountNo(accountNo)
                                .code(shortLinkCode)
                                .title(shortLinkAddRequest.getTitle())
                                .originalUrl(shortLinkAddRequest.getOriginalUrl())
                                .domain(domainDO.getValue())
                                .groupId(linkGroupDO.getId())
                                .expired(shortLinkAddRequest.getExpired())
                                .sign(originUrlDigest)
                                .state(ShortLinkStateEnum.ACTIVE.name())
                                .del(0)
                                .build();
                        // 将生成的短链数据插入数据库中
                        shortLinkManager.addShortLink(shortLinkDO);
                        return true;
                    }
                } else {
                    duplicateCodeFlag = true;
                    log.error("短链码重复：{}", eventMessage);
                }

            } else if (EventMessageTypeEnum.SHORT_LINK_ADD_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
                GroupCodeMappingDO  groupCodeMappingDOInDB = groupCodeMappingManager.findByCodeAndGroupId(shortLinkCode, linkGroupDO.getId(), accountNo);
                if (groupCodeMappingDOInDB == null) {
                    // B端处理
                    GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                            .accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(shortLinkAddRequest.getTitle())
                            .originalUrl(shortLinkAddRequest.getOriginalUrl())
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(shortLinkAddRequest.getExpired())
                            .sign(originUrlDigest)
                            .state(ShortLinkStateEnum.ACTIVE.name())
                            .del(0)
                            .build();
                    groupCodeMappingManager.addGroupMapping(groupCodeMappingDO);
                    return true;
                } else {
                    duplicateCodeFlag = true;
                    log.error("短链码重复：{}", eventMessage);
                }

            }
        } else {
            // 加锁失败，自选100毫秒;可能是短链码已经被占用了，需要重新生成
            log.error("加锁失败：{}", eventMessage);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            duplicateCodeFlag = true;
        }

        if (duplicateCodeFlag) {
            String newOriginUrl = CommonUtil.addUrlPrefixVersion(shortLinkAddRequest.getOriginalUrl());
            shortLinkAddRequest.setOriginalUrl(newOriginUrl);
            eventMessage.setContent(JsonUtil.obj2Json(shortLinkAddRequest));
            log.warn("短链码保存失败，重新生成:{}", eventMessage);
            handlerAddShortLink(eventMessage);
        }

        return false;
    }

    /**
     * 扣减流量包
     * @param eventMessage
     * @param shortLinkCode
     * @return
     */
    private boolean reduceTraffic(EventMessage eventMessage, String shortLinkCode) {
        UseTrafficRequest trafficRequest = UseTrafficRequest.builder().accountNo(eventMessage.getAccountNo()).bizId(shortLinkCode).build();
        JsonData jsonData = trafficFeignService.useTraffic(trafficRequest);
        if (jsonData.getCode() != 0) {
            log.error("流量包扣减失败：{}", eventMessage);
            return false;
        }
        return true;
    }

    @Override
    public boolean handlerUpdateShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkUpdateRequest request = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkUpdateRequest.class);

        DomainDO domainDO = checkDomain(request.getDomainType(), request.getDomainId(), accountNo);

        // C端处理
        if (EventMessageTypeEnum.SHORT_LINK_UPDATE_LINK.name().equalsIgnoreCase(eventMessageType)) {
            ShortLinkDO shortLinkDO = ShortLinkDO.builder()
                    .code(request.getCode())
                    .title(request.getTitle())
                    .domain(domainDO.getValue())
                    .accountNo(accountNo)
                    .build();
            int rows = shortLinkManager.update(shortLinkDO);
            log.info("更新成功rows={}", rows);
            return true;
        } else if (EventMessageTypeEnum.SHORT_LINK_UPDATE_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
            // B端处理
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                    .id(request.getMappingId())
                    .groupId(request.getGroupId())
                    .accountNo(accountNo)
                    .title(request.getTitle())
                    .domain(domainDO.getValue())
                    .build();
            int rows = groupCodeMappingManager.update(groupCodeMappingDO);
            log.info("更新成功rows={}", rows);
            return true;
        }

        return false;
    }

    @Override
    public boolean handlerDelShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String eventMessageType = eventMessage.getEventMessageType();
        ShortLinkDelRequest request = JsonUtil.json2Obj(eventMessage.getContent(), ShortLinkDelRequest.class);

        // C端处理
        if (EventMessageTypeEnum.SHORT_LINK_DEL_LINK.name().equalsIgnoreCase(eventMessageType)) {
            ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo).code(request.getCode()).build();
            int rows = shortLinkManager.deleteShortLink(shortLinkDO);
            log.info("更新成功rows={}", rows);
            return true;
        } else if (EventMessageTypeEnum.SHORT_LINK_DEL_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
            // B端处理
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                    .id(request.getMappingId())
                    .groupId(request.getGroupId())
                    .accountNo(accountNo)
                    .build();
            int rows = groupCodeMappingManager.delGroupMapping(groupCodeMappingDO);
            log.info("更新成功rows={}", rows);
            return true;
        }

        return false;
    }

    /**
     * 从B端查找，group_code_mapping表
     * @param request
     * @return
     */
    @Override
    public Map<String, Object> pageByGroupId(ShortLinkPageRequest request) {
        long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        Map<String, Object> result = groupCodeMappingManager.pageShortLinkByGroupId(request.getPage(), request.getSize(), accountNo, request.getGroupId());
        return result;
    }

    /**
     * 删除短链
     * @param request
     * @return
     */
    @Override
    public JsonData delLinkCode(ShortLinkDelRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageTypeEnum.SHORT_LINK_DEL.name())
                .build();

        // 发送消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkDelRoutingKey(), eventMessage);

        return JsonData.buildSuccess();
    }

    /**
     *  更新短链
     * @param request
     * @return
     */
    @Override
    public JsonData updateLink(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder()
                .accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageTypeEnum.SHORT_LINK_UPDATE.name())
                .build();

        // 发送消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkUpdateRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }

    /**
     * 检查短链类型是否合规
     *
     * @param domainType
     * @param domainId
     * @param accountNo
     * @return
     */
    private DomainDO checkDomain(String domainType, Long domainId, Long accountNo) {
        DomainDO domainDO = null;

        if (DomainTypeEnum.CUSTOM.name().equalsIgnoreCase(domainType)) {
            domainDO = domainManager.findById(domainId, accountNo);
        } else {
            domainDO = domainManager.findByDomainType(domainId, DomainTypeEnum.OFFICIAL);
        }

        Assert.notNull(domainDO, "域名不合法");
        return domainDO;
    }

    /**
     * 校验组名
     * @param groupId
     * @param accountNo
     * @return
     */
    private LinkGroupDO checkLinkGroup(Long groupId, Long accountNo) {
        LinkGroupDO groupDetails = linkGroupManager.getGroupDetails(groupId, accountNo);
        Assert.notNull(groupDetails, "组名不合法");
        return groupDetails;
    }


}
